﻿#include "ui/sliders.h"
#include "context.h"
#include "target.h"
#include "shared.h"
#include "draw.h"

using namespace std;
using namespace anip;

ui::Slider::Slider(Container* parent, invec2 pos, invec2 size,
	float min, float max, float val):
	_min(min), _max(max), _val(val)
{
	assert(min < max);

	if (parent != nullptr) parent->addChild(*this);
	setPosition(pos);
	setSize(size);
	setValue(val);
}

void ui::Slider::setRange(float min, float max)
{
	assert(min < max);
	_min = min;
	_max = max;
	setValue(_val);
}

void ui::Slider::setValue(float val)
{
	postSignal({ ui::Signal::Type::NeedsRedraw });
	if (integer) val = round(val);
	switch (boundaryBehavior)
	{
	case Boundary::AllowUnderflow:
		_val = min(_max, val);
		break;
	case Boundary::AllowOverflow:
		_val = max(_min, val);
		break;
	case Boundary::AllowNeither:
		if (canWarp)
		{
			float x = val - _min, y = _max - _min;
			if (x < 0)
				x += y * (int)(-x / y + 1);
			if (val == _max) _val = val;
			else _val = fmodf(x, y) + _min;
		}
		else
			_val = max(_min, min(_max, val));
		break;
	default:
		_val = val;
	}
}

void ui::Slider::submitEdit()
{
	//assert(_edit != nullptr);
	// ANIP_FIXME: 
	if (_edit == nullptr) return;

	float v = _val;
	if (parseFloat(_edit->getText(), v))
		setValue(v);
	_edit->markDelete();
	_edit = nullptr;
}

void ui::Slider::processMouseButton(MouseButton btn, ButtonAction action)
{
	if (btn == MouseButton::Left && action == ButtonAction::Up 
		&& !isDragging() && _edit == nullptr && canEdit)
	{
		_edit = new TextField((Container*)this);
		_edit->layoutOptions.dock = LayoutOptions::DockType::Fill;
		_edit->roundRadius = 3;
		_edit->backColor = Colors::transparent;
		_edit->borderColor = Colors::transparent;
		_edit->focusedBorderColor = Colors::transparent;
		_edit->getFont().size = min(_font.size, (int)(_size.y - 2 * r::RoundRadius));
		_edit->setText(toString(_val));
		_edit->setCursorIndex(_edit->getText().size());
		_edit->onKeyAction = [this](Control& self, Key k, ButtonAction action)
		{
			if ((k == Key::Enter || k == Key::KpEnter) && action == ButtonAction::Down)
				submitEdit();
		};
		_edit->focus();
	}

	Control::processMouseButton(btn, action);
}

void ui::Slider::processDefocus()
{
	if (_edit != nullptr)
		submitEdit();
}

bool ui::Slider::canBeginDrag(MouseButton btn, invec2 pos)
{
	if (_edit != nullptr) return false;

	_startVal = _val;
	if (btn == MouseButton::Left)
		_slowDrag = false;
	else if (btn == MouseButton::Middle)
		_slowDrag = true;
	else
		return false;

	return true;
}

void ui::Slider::processDrag(invec2 start, invec2 pos)
{
	if (vertical)
		setValue(_startVal
			+ (pos.y - start.y) / _size.y * (_max - _min) * (_slowDrag ? 0.1f : 1.0f));
	else
		setValue(_startVal 
			+ (pos.x - start.x) / _size.x * (_max - _min) * (_slowDrag ? 0.1f : 1.0f));

	if (onValueChanged) onValueChanged(*this);
}

void ui::Slider::processScroll(invec2 pos)
{
	if (vertical)
		setValue(_val + pos.y * 0.05f * (_max - _min));
	else
		setValue(_val - pos.y * 0.05f * (_max - _min));

	if (onValueChanged) onValueChanged(*this);
}

void ui::Slider::paint()
{
	Draw::roundedRect(_absPos, _size, r::RoundRadius, backColor);

	if (boundaryBehavior != Boundary::Unbounded)
	{
		float valpos = clamp((_val - _min) / (_max - _min), 0.0f, 1.0f);
		Draw::roundedRect(_absPos, 
			_size * (vertical ? vec2{ 1, valpos } : vec2{ valpos, 1 }), 
			r::RoundRadius, foreColor);
	}

	if (vertical)
	{
		// ANIP_UNDONE: vertical mode
	}

	if (_edit == nullptr)
	{
		Font font(_font);
		font.size = min(font.size, (int)(_size.y - 2 * r::RoundRadius));
		// ANIP_TODO: use XOR blend mode
		Draw::text(_absPos + vec2{ r::RoundRadius + 3, _size.y * 0.5f }, 
			toString(_val), Colors::black, font, { 0.0f, 0.5f });
	}
	else
	{
		_edit->paint();
	}
	
	if (spring > 0 && isDragging() && abs(defaultValue - _val) > 0.00001f)
	{
		postSignal({ ui::Signal::Type::Animation });
		float sp = powf(spring, (float)Context::deltaTime());
		setValue(_val * (1 - sp) + defaultValue * sp);
		if (onValueChanged)
			onValueChanged(*this);
	}
}

void ui::Slider::flow(Flow& f)
{
	if (layoutOptions.type != layoutOptions.Flow)
	{
		Control::flow(f);
		return;
	}

	float w, h;
	if (vertical)
	{
		w = r::Flow_Slider_MinHeight;
		h = r::Flow_Slider_Width;
	}
	else
	{
		w = min((float)r::Flow_Slider_Width, f.borderRight - f.currentX);
		h = max((float)r::Flow_Slider_MinHeight, f.lineHeight);
	}

	if (layoutOptions.stretchX > 0)
		setSize({ 
			(f.borderRight - f.currentX) * layoutOptions.stretchX, 
			max(h, layoutOptions.minSize.y) });
	else setSize({ 
		max(w, layoutOptions.minSize.x), 
		max(h, layoutOptions.minSize.y) });

	flowChildrenDefault();
	defaultFlowLayout(f);
}

void ui::Joystick::_layout()
{
	_dragSize = _size.x * 0.4f;
	_draggy->setPosition(
		_val * _size / 2.0f + _size / 2.0f - vec2{ _dragSize } / 2.0f);
	_draggy->setSize({ _dragSize, _dragSize });
}

ui::Joystick::Joystick(Container* parent, invec2 pos, invec2 size):
	_draggy(new Draggable<RoundButton>())
{
	addChild(*_draggy);
	_draggy->onBeginDrag = _onBeginDrag;
	_draggy->onDrag = _onDrag;
	_draggy->setSize({ _dragSize, _dragSize });
	_layout();

	if (parent != nullptr) parent->addChild(*this);
	setPosition(pos);
	setSize(size);
}

void ui::Joystick::paint()
{
	Draw::ellipse(_absPos, _size, backColor);
	_draggy->normalColor = buttonColor;
	_draggy->paint();

	if (spring > 0 && !_draggy->isDragging())
	{
		if (defaultValue != _val)
		{
			postSignal({ ui::Signal::Type::Animation });
			setValue(_val * (1 - spring) + defaultValue * spring);
			if (onValueChanged)
				onValueChanged(*this);
		}
	}
}

void ui::Joystick::flow(Flow& f)
{
	if (layoutOptions.type != layoutOptions.Flow)
	{
		Control::flow(f);
		return;
	}

	float maxs = f.borderRight - f.currentX;
	float size = min((float)r::Flow_Joystick_Size, maxs);

	if (layoutOptions.stretchX)
		setSize(vec2{ maxs, maxs } * layoutOptions.stretchX);
	else
		setSize({ size, size });

	if (f.currentX + _size.x > f.borderRight)
		f.newLine();
	f.lineHeight = max(f.lineHeight, _size.y);
	setPosition({ f.currentX, f.currentY });
	f.currentX += _size.x + r::Flow_Separator;
}

void ui::Joystick::_onDrag(Draggable<RoundButton>& self, 
	invec2 start, invec2 ppos)
{
	Joystick* joystick = (Joystick*)self.parent();
	float rad = joystick->_size.x / 2;
	vec2 pos = 
		(self.position() + ppos - start + joystick->_dragSize / 2) / rad - 1.0f;
	if (math::length(pos) > 1)
		pos = math::normalize(pos);

	self.setPosition(pos * rad + rad - joystick->_dragSize / 2);

	joystick->_val = pos;
	if (joystick->onValueChanged)
		joystick->onValueChanged(*joystick);
}